#include <stdio.h>
#include <string.h>
#include "vmchset.h"
#include "phjson.h"

/* ///////////////////////////////////////////////////////////////////////// */

typedef enum {
	OUTSIDE_OBJECT,
	INSIDE_OBJECT,
	INSIDE_STRING,
	EXPECT_COLON,
	EXPECT_VALUE,
	EXPECT_COMMA,
	OUTSIDE_VALUE,
	INSIDE_VALUE,
	INSIDE_ARRAY
} ph_json_parser_state;

#define STACK_SIZE 128

#define IS_BLANK_CHAR(ch)	(ch == ' ' || ch == '\t' || ch == '\r' || \
	ch == '\n')

static char *string, *value, err[128];

static VMINT value_len, string_len;

/* ///////////////////////////////////////////////////////////////////////// */

VMINT ph_json_parse(char *text, void (*callback)(ph_json_event_t event)) {
	VMINT i, col, row, result, stack[STACK_SIZE], stack2[STACK_SIZE], 
		stack_count, stack2_count, in_array;
	ph_json_parser_state state;

	for (i = result = col = stack_count = stack2_count = in_array = 0, 
		state = OUTSIDE_OBJECT, row = 1; text[i] != 0 && result == 0; i++) 
	{
		col++;
		if (text[i] == '\n') {
			col = 1;
			row++;
		}

		switch (state) {
		case OUTSIDE_OBJECT:
			if (text[i] == '{') {
				stack[stack_count++] = state;
				stack2[stack2_count++] = in_array;
				in_array = FALSE;
				state = INSIDE_OBJECT;
				callback(JSON_OBJ_BEGIN);
			}
			else if (!IS_BLANK_CHAR(text[i])) {
				result = PH_JSON_SYNTAX_ERROR;
				sprintf(err, "Expect '{' but found %c. [col=%d, row=%d]", 
					text[i], col, row);
				break;
			}
			break;
		case INSIDE_OBJECT:
			if (text[i] == '}') {
				state = stack[--stack_count];
				in_array = stack2[--stack2_count];
				callback(JSON_OBJ_END);
			}
			else if (text[i] == '"') {
				state = INSIDE_STRING;
				string = text + i + 1;
				string_len = 0;
			}
			else if (!IS_BLANK_CHAR(text[i])) {
				result = PH_JSON_SYNTAX_ERROR;
				sprintf(err, "Expect string but found %c. [col=%d, row=%d]",
					text[i], col, row);
				break;
			}
			break;
		case INSIDE_STRING:
			if (text[i] == '"') {
				callback(JSON_STRING);
				state = EXPECT_COLON;
			}
			else {
				string_len++;
			}
			break;
		case EXPECT_COLON:
			if (text[i] == ':') {
				state = EXPECT_VALUE;
			}
			else if (!IS_BLANK_CHAR(text[i])) {
				result = PH_JSON_SYNTAX_ERROR;
				sprintf(err, "Expect colon but found %c. [col=%d, row=%d]",
					text[i], col, row);
				break;
			}
			break;
		case EXPECT_VALUE:
			if (text[i] == '"') {
				state = INSIDE_VALUE;
				value = text + i + 1;
				value_len = 0;
			}
			else if (text[i] == '[') {
				in_array = TRUE;
				callback(JSON_ARRAY_BEGIN);
			}
			else if (text[i] == ',' && in_array) {

			}
			else if (text[i] == ']') {
				in_array = FALSE;
				state = INSIDE_OBJECT;
				callback(JSON_ARRAY_END);
			}
			else if (text[i] == '{') {
				stack[stack_count++] = state;
				stack2[stack2_count++] = in_array;
				in_array = FALSE;
				state = INSIDE_OBJECT;
				callback(JSON_OBJ_BEGIN);
			}
			else if (!IS_BLANK_CHAR(text[i])) {
				result = PH_JSON_SYNTAX_ERROR;
				sprintf(err, "Expect string or array or object, but found %c. [col=%d, row=%d]",
					text[i], col, row);
				break;
			}
			break;
		case EXPECT_COMMA:
			if (text[i] == ',') {
				if (in_array)
					state = EXPECT_VALUE;
				else
					state = INSIDE_OBJECT;
			}
			else if (text[i] == ']' && in_array) {
				in_array = FALSE;
				state = INSIDE_OBJECT;
				callback(JSON_ARRAY_END);
			}
			else if (text[i] == '}') {
				state = stack[--stack_count];
				in_array = stack2[--stack2_count];
				callback(JSON_OBJ_END);
			}
			else if (!IS_BLANK_CHAR(text[i])) {
				result = PH_JSON_SYNTAX_ERROR;
				sprintf(err, "Expect comma but found %c. [col=%d, row=%d]",
					text[i], col, row);
				break;
			}
			break;
		case INSIDE_VALUE:
			if (text[i] == '"') {
				state = EXPECT_COMMA;
				callback(JSON_VALUE);
			}
			else {
				value_len++;
			}
			break;
		}
	}
	
	return result;
}

void ph_json_get_string(char *str, VMINT size) {
	int len = string_len;

	if (len >= size)
		len = size - 1;

	memcpy(str, string, len);
	str[len] = 0;
}

void ph_json_get_value(char *v, VMINT size) {
	int len = value_len;

	if (len >= size)
		len = size - 1;

	memcpy(v, value, len);
	v[len] = 0;
}

char* ph_json_get_error(void) {
	return err;
}